//****** includes
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>
#include "mkListener.h"
#include "camera.h"
#include "Mesh.h"
#include "ModelManager.h"
#include "Network.h"
#include "Quad.h"
#include "Util.h"

#include <time.h>

using namespace std;

//****** include the Direct3D Library files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")
#pragma comment (lib, "dxguid.lib")

//****** global declarations
LPDIRECT3D9 d3d;
LPDIRECT3DDEVICE9 d3ddev;
mkListener mk;
camera *cam;
ModelManager modelManager;
Network* network;
Quad* scene;

//****** function prototypes
void init(HWND hWnd);
void renderFrame(void);
void cleanD3D(void);
void initGraphics(void);
void initLight(void);
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

//********************************************************************************************
// the entry point for the Windows program
//
//********************************************************************************************
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	::AllocConsole();
	Util::InitializeConsoleStdIO();
	

    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Simulation de trafic routier",
                          WS_EX_TOPMOST | WS_POPUP,    // fullscreen values
                          /*WS_OVERLAPPEDWINDOW,*/ 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    init(hWnd);
	mk.initDInput(hInstance, hWnd);

	// main loop
    MSG msg;

		int switchVisible=0;
		int switchFixed=0;

	
	while(TRUE)
	{
        while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        if(msg.message == WM_QUIT)
            break;
		
		// detect new inputs
		mk.detect_input();

		if(mk.getKeyState()[DIK_ESCAPE] & 0x80){
			break;
		}

		if((mk.getKeyState()[DIK_V] & 0x80) && switchVisible>10){
			network->switchCirclesVisible();
			switchVisible=0;
		}
		switchVisible++;
		if((mk.getKeyState()[DIK_F] & 0x80) && switchFixed>10){
			network->switchCirclesFixed();
			switchFixed=0;
		}
		switchFixed++;
		
		// render the frame
        renderFrame();
    }

    cleanD3D();
    mk.cleanDInput();

    return msg.wParam;

}

//********************************************************************************************
// this is the main message handler for the program
//
//********************************************************************************************
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
	{
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}

//********************************************************************************************
// initializes and prepares Direct3D for use
//
//********************************************************************************************
void init(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
	d3dpp.MultiSampleType = D3DMULTISAMPLE_8_SAMPLES;

    // create the d3d device
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_HARDWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    initGraphics();
    initLight();

    d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);
	// sets ambient light
    d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(150, 150, 150));
	// sets cull mode
	d3ddev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

	if(SUCCEEDED(d3d->CheckDeviceMultiSampleType(D3DADAPTER_DEFAULT, 
					D3DDEVTYPE_HAL , D3DFMT_R8G8B8, FALSE, 
					D3DMULTISAMPLE_8_SAMPLES, NULL)));

	// scene initialization
	scene = new Quad(cam);
	scene->import("quadville.txt");

	// network initialization
	network = new Network("importMap.str");
	network->setModelManager(modelManager);
	network->initTraficLight();
	network->roadSignOrientation();

	// adding some vehicle
/*	for (int i=0; i < 14 ;i++){
		Vehicle* v1 = new Vehicle(15,
								  network->listLane.at(i)->getIn()->getPosition()->getX(),
								  network->listLane.at(i)->getIn()->getPosition()->getY(),
								  0.2f,
								  0.1f,
								  MODEL_CAR,
								  0.1f);
		network->listLane.at(i)->addVehicle(v1);
	}*/

}

//********************************************************************************************
// this is the function used to render a single frame
//
//********************************************************************************************
void renderFrame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
    d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

	// update camera
	cam->update();
    
	// render the city
	//modelManager.drawCity();
	scene->render();
	
	// render the network
	network->update(cam->getPosX(),cam->getPosZ());
	network->render();

	network->renderCircle(cam->getPosX(),cam->getPosZ());

    d3ddev->EndScene(); 
    d3ddev->Present(NULL, NULL, NULL, NULL);
}

//********************************************************************************************
// cleans up Direct3D, and models
//
//********************************************************************************************
void cleanD3D(void)
{
	scene->release();
	modelManager.release();
    d3ddev->Release();
    d3d->Release();
}

//********************************************************************************************
// sets up the camera and modelManager
//
//********************************************************************************************
void initGraphics(void)
{
	cam = new camera(&mk,
					 &D3DXVECTOR3(0.0f,90.0f,0.0f), 
					 -90.0f, 0.0f, 
					 0.002f, 0.4f);

	modelManager.init();
}

//********************************************************************************************
// sets up the lights
//
//********************************************************************************************
void initLight(void)
{
    D3DLIGHT9 light,light2;

	// initialize first light
    ZeroMemory(&light, sizeof(light));
    light.Type = D3DLIGHT_DIRECTIONAL;
    light.Diffuse = D3DXCOLOR(0.9f, 0.9f, 0.9f, 1.0f);
    light.Direction = D3DXVECTOR3(0.3f, 0.5f, 0.3f);

	// initialize second light
	ZeroMemory(&light2, sizeof(light2));
    light2.Type = D3DLIGHT_DIRECTIONAL;
    light2.Diffuse = D3DXCOLOR(0.9f, 0.9f, 0.9f, 1.0f);
    light2.Direction = D3DXVECTOR3(0.3f, -0.5f, -0.3f);

	// add and turn on lights
    d3ddev->SetLight(0, &light);
	d3ddev->SetLight(1, &light2);
    d3ddev->LightEnable(0, TRUE);
	d3ddev->LightEnable(1, TRUE);
}
